// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/lib/ui/painting/path.h"

#include <cmath>

#include "flutter/lib/ui/painting/matrix.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/dart_args.h"
#include "third_party/tonic/dart_binding_macros.h"
#include "third_party/tonic/dart_library_natives.h"

using tonic::ToDart;

namespace flutter {

typedef CanvasPath Path;

IMPLEMENT_WRAPPERTYPEINFO(ui, Path);

CanvasPath::CanvasPath()
    : path_tracker_(UIDartState::Current()->GetVolatilePathTracker()),
      tracked_path_(std::make_shared<VolatilePathTracker::TrackedPath>()) {
  FML_DCHECK(path_tracker_);
  resetVolatility();
}

CanvasPath::~CanvasPath() = default;

void CanvasPath::resetVolatility() {
  if (!tracked_path_->tracking_volatility) {
    mutable_path().setIsVolatile(true);
    tracked_path_->frame_count = 0;
    tracked_path_->tracking_volatility = true;
    path_tracker_->Track(tracked_path_);
  }
}

int CanvasPath::getFillType() {
  return static_cast<int>(path().getFillType());
}

void CanvasPath::setFillType(int fill_type) {
  mutable_path().setFillType(static_cast<SkPathFillType>(fill_type));
  resetVolatility();
}

void CanvasPath::moveTo(float x, float y) {
  mutable_path().moveTo(x, y);
  resetVolatility();
}

void CanvasPath::relativeMoveTo(float x, float y) {
  mutable_path().rMoveTo(x, y);
  resetVolatility();
}

void CanvasPath::lineTo(float x, float y) {
  mutable_path().lineTo(x, y);
  resetVolatility();
}

void CanvasPath::relativeLineTo(float x, float y) {
  mutable_path().rLineTo(x, y);
  resetVolatility();
}

void CanvasPath::quadraticBezierTo(float x1, float y1, float x2, float y2) {
  mutable_path().quadTo(x1, y1, x2, y2);
  resetVolatility();
}

void CanvasPath::relativeQuadraticBezierTo(float x1,
                                           float y1,
                                           float x2,
                                           float y2) {
  mutable_path().rQuadTo(x1, y1, x2, y2);
  resetVolatility();
}

void CanvasPath::cubicTo(float x1,
                         float y1,
                         float x2,
                         float y2,
                         float x3,
                         float y3) {
  mutable_path().cubicTo(x1, y1, x2, y2, x3, y3);
  resetVolatility();
}

void CanvasPath::relativeCubicTo(float x1,
                                 float y1,
                                 float x2,
                                 float y2,
                                 float x3,
                                 float y3) {
  mutable_path().rCubicTo(x1, y1, x2, y2, x3, y3);
  resetVolatility();
}

void CanvasPath::conicTo(float x1, float y1, float x2, float y2, float w) {
  mutable_path().conicTo(x1, y1, x2, y2, w);
  resetVolatility();
}

void CanvasPath::relativeConicTo(float x1,
                                 float y1,
                                 float x2,
                                 float y2,
                                 float w) {
  mutable_path().rConicTo(x1, y1, x2, y2, w);
  resetVolatility();
}

void CanvasPath::arcTo(float left,
                       float top,
                       float right,
                       float bottom,
                       float startAngle,
                       float sweepAngle,
                       bool forceMoveTo) {
  mutable_path().arcTo(SkRect::MakeLTRB(left, top, right, bottom),
                       startAngle * 180.0 / M_PI, sweepAngle * 180.0 / M_PI,
                       forceMoveTo);
  resetVolatility();
}

void CanvasPath::arcToPoint(float arcEndX,
                            float arcEndY,
                            float radiusX,
                            float radiusY,
                            float xAxisRotation,
                            bool isLargeArc,
                            bool isClockwiseDirection) {
  const auto arcSize = isLargeArc ? SkPath::ArcSize::kLarge_ArcSize
                                  : SkPath::ArcSize::kSmall_ArcSize;
  const auto direction =
      isClockwiseDirection ? SkPathDirection::kCW : SkPathDirection::kCCW;

  mutable_path().arcTo(radiusX, radiusY, xAxisRotation, arcSize, direction,
                       arcEndX, arcEndY);
  resetVolatility();
}

void CanvasPath::relativeArcToPoint(float arcEndDeltaX,
                                    float arcEndDeltaY,
                                    float radiusX,
                                    float radiusY,
                                    float xAxisRotation,
                                    bool isLargeArc,
                                    bool isClockwiseDirection) {
  const auto arcSize = isLargeArc ? SkPath::ArcSize::kLarge_ArcSize
                                  : SkPath::ArcSize::kSmall_ArcSize;
  const auto direction =
      isClockwiseDirection ? SkPathDirection::kCW : SkPathDirection::kCCW;
  mutable_path().rArcTo(radiusX, radiusY, xAxisRotation, arcSize, direction,
                        arcEndDeltaX, arcEndDeltaY);
  resetVolatility();
}

void CanvasPath::addRect(float left, float top, float right, float bottom) {
  mutable_path().addRect(SkRect::MakeLTRB(left, top, right, bottom));
  resetVolatility();
}

void CanvasPath::addOval(float left, float top, float right, float bottom) {
  mutable_path().addOval(SkRect::MakeLTRB(left, top, right, bottom));
  resetVolatility();
}

void CanvasPath::addArc(float left,
                        float top,
                        float right,
                        float bottom,
                        float startAngle,
                        float sweepAngle) {
  mutable_path().addArc(SkRect::MakeLTRB(left, top, right, bottom),
                        startAngle * 180.0 / M_PI, sweepAngle * 180.0 / M_PI);
  resetVolatility();
}

void CanvasPath::addPolygon(const tonic::Float32List& points, bool close) {
  mutable_path().addPoly(reinterpret_cast<const SkPoint*>(points.data()),
                         points.num_elements() / 2, close);
  resetVolatility();
}

void CanvasPath::addRRect(const RRect& rrect) {
  mutable_path().addRRect(rrect.sk_rrect);
  resetVolatility();
}

void CanvasPath::addPath(CanvasPath* path, double dx, double dy) {
  if (!path) {
    Dart_ThrowException(ToDart("Path.addPath called with non-genuine Path."));
    return;
  }
  mutable_path().addPath(path->path(), dx, dy, SkPath::kAppend_AddPathMode);
  resetVolatility();
}

void CanvasPath::addPathWithMatrix(CanvasPath* path,
                                   double dx,
                                   double dy,
                                   Dart_Handle matrix4_handle) {
  tonic::Float64List matrix4(matrix4_handle);

  if (!path) {
    matrix4.Release();
    Dart_ThrowException(
        ToDart("Path.addPathWithMatrix called with non-genuine Path."));
    return;
  }

  SkMatrix matrix = ToSkMatrix(matrix4);
  matrix4.Release();
  matrix.setTranslateX(matrix.getTranslateX() + dx);
  matrix.setTranslateY(matrix.getTranslateY() + dy);
  mutable_path().addPath(path->path(), matrix, SkPath::kAppend_AddPathMode);
  resetVolatility();
}

void CanvasPath::extendWithPath(CanvasPath* path, double dx, double dy) {
  if (!path) {
    Dart_ThrowException(
        ToDart("Path.extendWithPath called with non-genuine Path."));
    return;
  }
  mutable_path().addPath(path->path(), dx, dy, SkPath::kExtend_AddPathMode);
  resetVolatility();
}

void CanvasPath::extendWithPathAndMatrix(CanvasPath* path,
                                         double dx,
                                         double dy,
                                         Dart_Handle matrix4_handle) {
  tonic::Float64List matrix4(matrix4_handle);

  if (!path) {
    matrix4.Release();
    Dart_ThrowException(
        ToDart("Path.addPathWithMatrix called with non-genuine Path."));
    return;
  }

  SkMatrix matrix = ToSkMatrix(matrix4);
  matrix4.Release();
  matrix.setTranslateX(matrix.getTranslateX() + dx);
  matrix.setTranslateY(matrix.getTranslateY() + dy);
  mutable_path().addPath(path->path(), matrix, SkPath::kExtend_AddPathMode);
  resetVolatility();
}

void CanvasPath::close() {
  mutable_path().close();
  resetVolatility();
}

void CanvasPath::reset() {
  mutable_path().reset();
  resetVolatility();
}

bool CanvasPath::contains(double x, double y) {
  return path().contains(x, y);
}

void CanvasPath::shift(Dart_Handle path_handle, double dx, double dy) {
  fml::RefPtr<CanvasPath> path = Create(path_handle);
  auto& other_mutable_path = path->mutable_path();
  mutable_path().offset(dx, dy, &other_mutable_path);
  resetVolatility();
}

void CanvasPath::transform(Dart_Handle path_handle,
                           Dart_Handle matrix4_handle) {
  tonic::Float64List matrix4(matrix4_handle);
  auto sk_matrix = ToSkMatrix(matrix4);
  matrix4.Release();
  fml::RefPtr<CanvasPath> path = Create(path_handle);
  auto& other_mutable_path = path->mutable_path();
  mutable_path().transform(sk_matrix, &other_mutable_path);
}

tonic::Float32List CanvasPath::getBounds() {
  tonic::Float32List rect(Dart_NewTypedData(Dart_TypedData_kFloat32, 4));
  const SkRect& bounds = path().getBounds();
  rect[0] = bounds.left();
  rect[1] = bounds.top();
  rect[2] = bounds.right();
  rect[3] = bounds.bottom();
  return rect;
}

bool CanvasPath::op(CanvasPath* path1, CanvasPath* path2, int operation) {
  return Op(path1->path(), path2->path(), static_cast<SkPathOp>(operation),
            &tracked_path_->path);
  resetVolatility();
}

void CanvasPath::clone(Dart_Handle path_handle) {
  fml::RefPtr<CanvasPath> path = Create(path_handle);
  // per Skia docs, this will create a fast copy
  // data is shared until the source path or dest path are mutated
  path->mutable_path() = this->path();
}

}  // namespace flutter
